/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkClass, ArkFile } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { ClassCategory } from "arkanalyzer/lib/core/model/ArkClass";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'MaxClassesPerFileCheck');
type Options = [{
    max: number;
    ignoreExpressions: boolean;
}]
const MAX_DEPTH = 1;
export class MaxClassesPerFileCheck implements BaseChecker {
    metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: "docs/max-classes-per-file-check.md",
        description: `Maximum allowed is ${MAX_DEPTH}`
    };
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private defaultOptions: Options = [{ "max": MAX_DEPTH, "ignoreExpressions": false }];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    public registerMatchers(): MatcherCallback[] {
        const matchClassCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchClassCb];
    }

    public check = (targetFile: ArkFile) => {
        this.defaultOptions = this.rule && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        if (targetFile instanceof ArkFile) {
            let fileClasses: ArkClass[] = targetFile.getClasses();
            // 配置了可选项ignoreExpressions属性 class没有命名的标记符用%AC$%dflt$%dflt$判断，以及嵌套class的className中包含%AC$VisitorFactory$forDescriptor$
            if (this.defaultOptions[0].ignoreExpressions) {
                fileClasses = fileClasses.filter(objClass =>
                    !(
                        objClass.getSignature().getClassName().includes('%AC$%dflt$%dflt$') ||
                        objClass.getSignature().getClassName().includes('%AC$VisitorFactory$forDescriptor$')
                    )
                );
            }
            fileClasses = fileClasses.filter(objClass => (objClass.getCategory() === ClassCategory.CLASS && objClass.getCode() !== undefined));
            let reported = false;
            if (fileClasses.length > (this.defaultOptions[0].max ?? MAX_DEPTH)) {
                for (const objClass of fileClasses) {
                    const line = objClass.getLine();
                    const startCol = objClass.getColumn();
                    if (objClass.getCode() && !reported) {
                        reported = true;
                        this.addIssueReport(fileClasses.length, line, startCol, targetFile);
                    }
                }
            }
        }
    };
    private addIssueReport(classNumber: number, line: number, startCol: number, arkFile: ArkFile) {
        const originPath = arkFile.getFilePath();
        if (!originPath) {
            logger.debug('arkFile is null');
        }
        const severity = this.rule.alert ?? this.metaData.severity;
        this.metaData.description = `File has too many classes (${classNumber}). Maximum allowed is ${(this.defaultOptions[0].max ?? MAX_DEPTH)}.`;
        const fixKey = line + '%' + startCol + '%' + this.rule.ruleId;
        const mergeKey = originPath + '%' + fixKey + '%' + this.metaData.description;
        const defect = new Defects(line, startCol, this.metaData.description, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}
